﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MovieRental.DataInterfaces;
using System.Data.Entity;
using MovieRental.GeneralDataExceptions;
using MovieRental.EfRepositoryLibrary.Mappers;
using System.Data;
using System.Data.Objects;
using System.Data.Objects.DataClasses;

namespace MovieRental.EfRepositoryLibrary
{
    public abstract class EfRepository<TEntity, TKey, TModel, TBase> : IRepository<TEntity, TKey> 
        where TKey : IEquatable<TKey>
        where TEntity : class, new()
        where TModel : TBase, new()
        where TBase : EntityObject, new()
    {
        protected string _connectionString;
        protected IEntityModelMapper<TEntity, TKey, TModel, TBase, MovieRentalEntitiesContainer>  _mapper;

        public EfRepository(string connectionString)
        {
            _connectionString = connectionString;
        }
        
        public MovieRentalEntitiesContainer CreateContext()
        {
            return new MovieRentalEntitiesContainer(_connectionString);
        }

        public virtual TEntity Load(TKey id)
        {
            using (var context = CreateContext())
            {
                try
                {
                    var model =_mapper.SelectObjectQuery(context).AsEnumerable().Single(x => _mapper.SelectKey(x).Equals(id));
                    return _mapper.ConvertBack(model);
                }
                catch (Exception ex)
                {
                    throw new ItemNotFoundException(id.ToString(), ex);
                }
            }
        }

        public virtual void Save(TEntity entity)
        {
            using (var context = CreateContext())
            {
                var objectSet = _mapper.SelectObjectSet(context);
                TKey id = _mapper.SelectKey(entity);
                var model = objectSet.OfType<TModel>().AsEnumerable().SingleOrDefault(x => _mapper.SelectKey(x).Equals(id));
                if (model == null)
                {
                    model = _mapper.Convert(entity);
                    objectSet.AddObject(model);
                }
                else
                    _mapper.Convert(entity, model);

                try
                { 
                    context.SaveChanges();
                    if (!_mapper.SelectKey(model).Equals(_mapper.SelectKey(entity)))
                        _mapper.ConvertBack(model, entity);
                }
                catch (Exception ex)
                {
                    throw new DbConnectionException("Can't save object in the db.", ex);
                }
                

            }
        }

        public virtual void Delete(TEntity entity)
        {
            Delete(_mapper.SelectKey(entity));
        }

        public virtual void Delete(TKey id)
        {
            using (var context = CreateContext())
            {
                var objectSet = _mapper.SelectObjectSet(context);
                var model = objectSet.OfType<TModel>().AsEnumerable().Single(x => _mapper.SelectKey(x).Equals(id));
                objectSet.DeleteObject(model);
                try
                {
                    context.SaveChanges();
                }
                catch (Exception ex)
                {
                    throw new ItemNotFoundException(id.ToString(), ex);
                }
            }
        }

        public virtual IEnumerable<TEntity> LoadAll()
        {
            using (var context = CreateContext())
            {
                try
                {
                    return _mapper.SelectObjectQuery(context).AsEnumerable().Select(x => _mapper.ConvertBack(x)).ToList();
                }
                catch (Exception ex)
                {
                    throw new DbConnectionException("Can't load objects from the db.", ex);
                }
            }
            
        }
    }
}
